import java.util.*;
import java.math.*;

public class CircuitController extends Thread{
    
    HashMap<Integer, Player> players;
    HashMap<Integer, User> users;

    ArrayList<InputGate> inputGates;    
    ArrayList<ConstGate> constGates;    
    ArrayList<AddGate> addGates;    
    ArrayList<MultGate> multGates;
    ArrayList<OutputGate> outputGates;

    HashMap<Integer, Integer> inputMap, multMap, outMap;
    //only temporary - should be removed once the real getNext*Gates
    //comes:
    int constCount=0, inputCount=0, addCount=0, multCount=0, outCount=0;
    boolean inputReady = false, multReady = false, outReady = false;
    int arg, mGates;

    /**
     * In charge of handling the circuit - getting the gates needed,
     * and is the main control unit of the program. 
     */
    public CircuitController(int arg, int mGates){
        this.arg = arg;
        this.mGates = mGates;
        inputMap = new HashMap<Integer, Integer>();
        multMap = new HashMap<Integer, Integer>();
        outMap = new HashMap<Integer, Integer>();
    }

    public void run(){
        inputGates = new ArrayList<InputGate>();
        for(int i = 1; i <= Utilities.n; i++){
            InputGate inp = new InputGate(i);
            inputGates.add(inp);
        }

        //For now - create n constant gates... not so important as of
        //now...
        constGates = new ArrayList<ConstGate>();
        for(int i = 1; i <= Utilities.n; i++){
            ConstGate con = new ConstGate(i, BigInteger.valueOf(i));
            constGates.add(con);
        }
        
        if(arg == 1){
            //For now, let the add gates be a test system where every
            //input is added and outputted to all users. This is 3 gates
            addGates = new ArrayList<AddGate>();
            
            AddGate add1 = new AddGate(1, inputGates.get(0), inputGates.get(1));
            AddGate add2 = new AddGate(2, inputGates.get(2), inputGates.get(3));
            AddGate add3 = new AddGate(3, add1, add2);
            
            addGates.add(add1);
            addGates.add(add2);
            addGates.add(add3);

            multGates = new ArrayList<MultGate>();
            
            MultGate mul1 = new MultGate(1, add3, constGates.get(1));
            MultGate mul2 = new MultGate(2, mul1, constGates.get(1));
            
            multGates.add(mul1);
            multGates.add(mul2);  

            outputGates = new ArrayList<OutputGate>();
            
            for(int i = 1; i <= Utilities.n; i++){
                //everyone receives the same output
                OutputGate out = new OutputGate(i, mul2);
                outputGates.add(out);
            }        
        }
        else if(arg == 2){
            multGates = new ArrayList<MultGate>();
            for(int i = 1; i<= mGates; i++){
                MultGate mul = new MultGate(i,constGates.get(2), constGates.get(1)); //3*2=6
                multGates.add(mul);
            }
            
            outputGates = new ArrayList<OutputGate>();
            
            for(int i = 1; i <= Utilities.n; i++){
                //everyone receives the same output
                OutputGate out = new OutputGate(i, multGates.get(0)); //should be 6
                outputGates.add(out);
            }
        }
        else if(arg == 3){
            multGates = new ArrayList<MultGate>();
            MultGate mul1 = new MultGate(1, constGates.get(2), constGates.get(1));
            multGates.add(mul1);
            int counter = 0;
            for(int i = 2; i<= mGates; i++){
                //MultGate mul = new MultGate(i,multGates.get(counter), constGates.get(1)); //3*2=6
                MultGate mul = new MultGate(i,multGates.get(counter), multGates.get(counter)); //3*2=6
                counter++;
                multGates.add(mul);
            }
            
            outputGates = new ArrayList<OutputGate>();
            
            for(int i = 1; i <= Utilities.n; i++){
                //everyone receives the same output
                OutputGate out = new OutputGate(i, multGates.get(0)); //should be 6
                outputGates.add(out);
            }
        }
    }

    public void setPlayers(HashMap<Integer, Player> players){
        this.players = players;
    }
    
    public void setUsers(HashMap<Integer, User> users){
        this.users = users;
    }

    public ArrayList<ConstGate> getNextConstGates(){        
        if(constCount >= 1){
            return null;
        }
        constCount++;
        return constGates;
    }

    public ArrayList<InputGate> getNextInputGates(){
         if(inputCount >= 1){
             return null;
         }
         inputCount++;
         return inputGates;
    }

    public ArrayList<AddGate> getNextAddGates(){
        if(arg == 1){
            if(addCount >= 1){
                return null;
            }
            addCount++;
            
            return addGates;
        }
        else
            return null;
    }

    /**
     * @return only the multiplication gates that are ready.
     */
    public ArrayList<MultGate> getNextMultGates(){
        ArrayList<MultGate> res = new ArrayList<MultGate>();
        if(arg == 1){
            if(multCount == 0)
                res.add(multGates.get(0));        
            else if(multCount == 1)
                res.add(multGates.get(1));            
            else
                return null;
            multCount++;         
            return res;
        }
        else if(arg == 2){
            if(multCount == 1)
                return null;
            multCount++;
            return multGates;
        }
        else{
            if(multCount == mGates){
                return null;
            }
            else{
                res.add(multGates.get(multCount));
                multCount++;
                return res;
            }
        }
    }

    public ArrayList<OutputGate> getNextOutputGates(){
        if(outCount >= 1){
             return null;
         }
         outCount++;
         
        return outputGates;
    }

    /**
     * Make sure that the preparation phase is run before this
     * computation phase.
     */
    public void runCircuit(){
        //first go through all const gates, then input, then a loop
        //where we take all add gates first, then multiplication
        //gates. After all those are done, we know all output gates
        //are ready, and thus we evaluate those. 

        ArrayList<ConstGate> constantGates;
        while( (constantGates = getNextConstGates()) != null){
            for(ConstGate g: constantGates){
                //Do whatever we need to do with const gates.. if
                //anything?
                for(Integer ID: players.keySet()){
                    players.get(ID).constGate(g);
                }
            }
        }

        ArrayList<InputGate> inputs;
        while( (inputs = getNextInputGates()) != null){
            for(int i = 0; i< inputs.size(); i++){
                //Do whatever we need to do with input gates..
                for(Integer ID : users.keySet()){
                    //send to player i+1 since players ID start at 1, not 0.
                    users.get(ID).reconsPriv(i+1, Utilities.INPUTGATE, inputs.get(i));
                }
                while(!inputReady){
                    yield();
                }
                inputReady = false;
            }
        }
        try{
            wait(500);
        }
        catch(Exception e){}

        //Loop until no more add or mult gates exist:
        ArrayList<AddGate> additions;
        ArrayList<MultGate> multiplications;
        while(true){
            additions = getNextAddGates();
            multiplications = getNextMultGates();
            if(additions == null && multiplications == null){
                //No more add or mult gates available. Continuing to
                //outputGates
                break;
            }
            //Loop addition gates            
            if(additions != null){
                do{
                    for(AddGate g : additions){
                        for(Integer ID: players.keySet()){
                            players.get(ID).addGate(g);
                        }
                    }
                }while( (additions = getNextAddGates()) != null);
            }

            //loop multiplication gates
            if(multiplications != null){
                do{
                    ArrayList<MultGate> gates = new ArrayList<MultGate>();
                    for(int i = 0; i<multiplications.size(); i++){
                        //gather T/2 gates, or those that are left, and do them in parallel
                        if(gates.size() < Utilities.T/2){
                            gates.add(multiplications.get(i));
                        }
                        if(gates.size() == Utilities.T/2 || i == multiplications.size()-1){
                            for(Integer ID: players.keySet()){
                                players.get(ID).multGate(gates);
                            }
                            gates.clear();
                            while(!multReady){
                                //Wait for players to do the reconsPubl
                                //and calculations
                                yield();
                            }
                            //reset multReady..
                            multReady = false;
                        }
                    }
                }while( (multiplications = getNextMultGates()) != null);
            }
        }
        ArrayList<OutputGate> outgates;
        while( (outgates = getNextOutputGates()) != null){
            for(OutputGate g: outgates){                
                for(Integer ID: users.keySet()){
                    users.get(ID).reconsPriv(g.getID(), Utilities.OUTPUTGATE, g);
                }                    
            }
        }

        while(!testReady){
            yield();
        }

        for(Integer ID : users.keySet()){
            users.get(ID).p.test(); //each users player
        }        
    }

    public void continueInputGates(int ID){
        if(inputMap.containsKey(ID)){
            //ID already there.. exiting
            System.err.println("Error - same ID trying to bogus something in inp.. "+ID);
            System.exit(-1);
        }
        inputMap.put(ID,ID);
        if(inputMap.size() == Utilities.n_prime){
            inputMap.clear();
            inputReady = true;
        }
    }

    public void continueMultGates(int ID){
        if(multMap.containsKey(ID)){
            //ID already there.. shutting down
            System.err.println("Error - same ID trying to bogus something in mult.. " + ID);
            System.exit(-1);
        }
        multMap.put(ID,ID);
        if(multMap.size() == Utilities.n_prime){
            multMap.clear();
            multReady = true;
        }
    }

    public void continueOutputGates(int ID){
        if(outMap.containsKey(ID)){
            //ID already there.. shutting down
            System.err.println("Error - same ID trying to bogus something in out.. " + ID);
            //System.exit(-1);            
        }
        outMap.put(ID,ID);
        if(outMap.size() == Utilities.n_prime){ 
            outMap.clear();
            outReady = true;
        }
    }

    HashMap<Integer, Integer> testMap = new HashMap<Integer, Integer>();
    boolean testReady = false;
    public void continueToTest(int ID){
        testMap.put(ID, ID);
        if(testMap.size() == Utilities.n){
            testMap.clear();
            testReady = true;
        }
    }

}